En omfattande guide till modulaugmentering i TypeScript för att utöka typer i tredjepartsbibliotek, förbÀttra kodsÀkerhet och utvecklarupplevelse globalt.
Modulaugmentering: Sömlös utökning av typer i tredjepartsbibliotek
I den dynamiska vÀrlden av mjukvaruutveckling förlitar vi oss ofta pÄ ett rikt ekosystem av tredjepartsbibliotek för att accelerera vÄra projekt. Dessa bibliotek tillhandahÄller förbyggda funktioner som sparar oss enormt mycket utvecklingstid. En vanlig utmaning uppstÄr dock nÀr de typer som biblioteken tillhandahÄller inte riktigt matchar vÄra specifika behov, eller nÀr vi vill integrera dem djupare i applikationens typsystem. Det Àr hÀr modulaugmentering i TypeScript briljerar, och erbjuder en kraftfull och elegant lösning för att utöka och förbÀttra typerna av befintliga moduler utan att modifiera deras ursprungliga kÀllkod.
FörstÄ behovet av typ-utökning
FörestĂ€ll dig att du arbetar med en internationell e-handelsplattform. Du anvĂ€nder ett populĂ€rt date-fns-bibliotek för alla dina datummanipulationsbehov. Din applikation krĂ€ver specifik formatering för olika regioner, kanske visas datum i formatet "DD/MM/YYYY" för Europa och "MM/DD/YYYY" för Nordamerika. Ăven om date-fns Ă€r otroligt mĂ„ngsidigt, kanske dess standardtypdefinitioner inte direkt exponerar en anpassad formateringsfunktion som följer din applikations specifika lokala konventioner.
Alternativt, övervÀg att integrera med ett betalningsgateway-SDK. Detta SDK kan exponera ett generiskt PaymentDetails-grÀnssnitt. Din applikation kan dock behöva lÀgga till proprietÀra fÀlt som loyaltyPointsEarned eller customerTier till detta PaymentDetails-objekt för intern spÄrning. Att direkt modifiera SDK:ns typer Àr ofta opraktiskt, sÀrskilt om du inte hanterar SDK:ns kÀllkod eller om den ofta uppdateras.
Dessa scenarier belyser ett grundlÀggande behov: förmÄgan att augmentera eller utöka typerna av extern kod för att anpassa sig till vÄr applikations unika krav och för att förbÀttra typsÀkerheten och utvecklingsverktygen för dina globala utvecklingsteam.
Vad Àr modulaugmentering?
Modulaugmentering Àr en TypeScript-funktion som lÄter dig lÀgga till nya egenskaper eller metoder till befintliga moduler eller grÀnssnitt. Det Àr en form av deklarationssammanslagning, dÀr TypeScript kombinerar flera deklarationer för samma entitet till en enda, enhetlig definition.
Det finns tvÄ huvudsakliga sÀtt modulaugmentering manifesteras i TypeScript:
- Augmentering av namnomrÄden: Detta Àr anvÀndbart för Àldre JavaScript-bibliotek som exponerar globala objekt eller namnomrÄden.
- Augmentering av moduler: Detta Àr det vanligaste och modernaste tillvÀgagÄngssÀttet, sÀrskilt för bibliotek distribuerade via npm som anvÀnder ES-modulsyntax.
I syfte att utöka tredjepartsbibliotekens typer Àr augmentering av moduler vÄrt primÀra fokus.
Augmentering av moduler: KĂ€rnkonceptet
Syntaxen för att augmentera en modul Àr enkel. Du skapar en ny .d.ts-fil (eller inkluderar augmenteringen i en befintlig) och anvÀnder en speciell import-syntax:
// For example, if you want to augment the 'lodash' module
import 'lodash';
declare module 'lodash' {
interface LoDashStatic {
// Add new methods or properties here
myCustomUtility(input: string): string;
}
}
LÄt oss bryta ner detta:
import 'lodash';: Denna rad Ă€r avgörande. Den talar om för TypeScript att du avser att augmentera modulen 'lodash'. Ăven om den inte utför nĂ„gon kod vid körning, signalerar den till TypeScript-kompilatorn att denna fil Ă€r relaterad till 'lodash'-modulen.declare module 'lodash' { ... }: Detta block innesluter dina augmenteringar för 'lodash'-modulen.interface LoDashStatic { ... }: Inutideclare module-blocket kan du deklarera nya grĂ€nssnitt eller slĂ„ ihop med befintliga som tillhör modulen. För bibliotek som lodash har huvudexporten ofta en typ somLoDashStatic. Du mĂ„ste inspektera bibliotekets typdefinitioner (ofta finns inode_modules/@types/library-name/index.d.ts) för att identifiera rĂ€tt grĂ€nssnitt eller typ att augmentera.
Efter denna deklaration kan du anvÀnda din nya myCustomUtility-funktion som om den vore en del av lodash:
import _ from 'lodash';
const result = _.myCustomUtility('hello from the world!');
console.log(result); // Output: 'hello from the world!' (assuming your implementation returns the input)
Viktig notering: Modulaugmentering i TypeScript Àr rent en kompileringstidsfunktion. Den lÀgger inte till funktionalitet i JavaScript-körtiden. För att dina augmenterade metoder eller egenskaper faktiskt ska fungera, mÄste du tillhandahÄlla en implementering. Detta görs vanligtvis i en separat JavaScript- eller TypeScript-fil som importerar den augmenterade modulen och fÀster din anpassade logik till den.
Praktiska exempel pÄ modulaugmentering
Exempel 1: Augmentering av ett datumbibliotek för anpassad formatering
LÄt oss Äterbesöka vÄrt datumformateringsexempel. Anta att vi anvÀnder biblioteket date-fns. Vi vill lÀgga till en metod för att formatera datum till ett konsekvent "DD/MM/YYYY"-format globalt, oavsett anvÀndarens lokala instÀllning i webblÀsaren. Vi antar att date-fns-biblioteket har en format-funktion, och vi vill lÀgga till ett nytt, specifikt formatval.
1. Skapa en deklarationsfil (t.ex. src/types/date-fns.d.ts):
// src/types/date-fns.d.ts
// Import the module to signal augmentation.
// This line doesn't add any runtime code.
import 'date-fns';
declare module 'date-fns' {
// We'll augment the main export, which is often a namespace or object.
// For date-fns, it's common to work with functions directly, so we might
// need to augment a specific function or the module's export object.
// Let's assume we want to add a new format function.
// We need to find the correct place to augment. Often, libraries export
// a default object or a set of named exports. For date-fns, we can augment
// the module's default export if it's used that way, or specific functions.
// A common pattern is to augment the module itself if specific exports aren't directly accessible for augmentation.
// Let's illustrate augmenting a hypothetical 'format' function if it were a method on a Date object.
// More realistically, we augment the module to potentially add new functions or modify existing ones.
// For date-fns, a more direct approach might be to declare a new function
// in a declaration file that uses date-fns internally.
// However, to demonstrate module augmentation properly, let's pretend date-fns
// has a global-like object we can extend.
// A more accurate approach for date-fns would be to add a new function signature
// to the module's known exports if we were to modify the core library's types.
// Since we're extending, let's show how to add a new named export.
// This is a simplified example assuming we want to add a `formatEuropeanDate` function.
// In reality, date-fns exports functions directly. We can add our function to the module's exports.
// To augment the module with a new function, we can declare a new type for the module export.
// If the library is commonly imported as `import * as dateFns from 'date-fns';`,
// we'd augment `DateFns` namespace. If imported as `import dateFns from 'date-fns';`,
// we'd augment the default export type.
// For date-fns, which exports functions directly, you'd typically define your own
// function that uses date-fns internally. However, if the library structure allowed
// for it (e.g., it exported an object of utilities), you could augment that object.
// Let's demonstrate augmenting a hypothetical utility object.
// If date-fns exposed something like `dateFns.utils.formatDate`, we could do:
// interface DateFnsUtils {
// formatEuropeanDate(date: Date): string;
// }
// interface DateFns {
// utils: DateFnsUtils;
// }
// A more practical approach for date-fns is to leverage its `format` function and add
// a new format string or create a wrapper function.
// Let's show how to augment the module to add a new formatting option for the existing `format` function.
// This requires knowing the internal structure of `format` and its accepted format tokens.
// A common technique is to augment the module with a new named export, if the library supports it.
// Let's assume we are adding a new utility function to the module's exports.
// We'll augment the module itself to add a new named export.
// First, let's try to augment the module's export itself.
// If date-fns was structured like: `export const format = ...; export const parse = ...;`
// We can't directly add to these. Module augmentation works by merging declarations.
// The most common and correct way to augment modules like date-fns is to
// use the module augmentation to declare additional functions or modify
// existing ones *if* the library's types allow for it.
// Let's consider a simpler case: extending a library that exports an object.
// Example: If `libraryX` exports `export default { methodA: () => {} };`
// `declare module 'libraryX' { interface LibraryXExport { methodB(): void; } }`
// For date-fns, let's illustrate by adding a new function to the module.
// This is done by declaring the module and then adding a new member to its export interface.
// However, date-fns exports functions directly, not an object to be augmented this way.
// A better way to achieve this for date-fns is by creating a new declaration file that
// augments the module's capabilities by adding a new function signature.
// Let's assume we are augmenting the module to add a new top-level function.
// This requires understanding how the module is intended to be extended.
// If we want to add a `formatEuropeanDate` function:
// This is best done by defining your own function and importing date-fns within it.
// However, to force the issue of module augmentation for the sake of demonstration:
// We'll augment the module 'date-fns' to include a new function signature.
// This approach assumes the module exports are flexible enough.
// A more realistic scenario is augmenting a type returned by a function.
// Let's assume date-fns has a main object export and we can add to it.
// (This is a hypothetical structure for demonstration)
// declare namespace dateFnsNamespace { // If it was a namespace
// function format(date: Date, formatString: string): string;
// function formatEuropeanDate(date: Date): string;
// }
// For practical date-fns augmentation: you might extend the `format` function's
// capabilities by declaring a new format token it understands.
// This is advanced and depends on the library's design.
// A simpler, more common use case: extending a library's object properties.
// Let's pivot to a more common example that fits module augmentation directly.
// Suppose we use a hypothetical `apiClient` library.
}
Korrigering och mer realistiskt exempel för datumbibliotek:
För bibliotek som date-fns, som exporterar individuella funktioner, Àr direkt modulaugmentering för att lÀgga till nya toppnivÄfunktioner inte det idiomatiska sÀttet. IstÀllet anvÀnds modulaugmentering bÀst nÀr biblioteket exporterar ett objekt, en klass eller ett namnomrÄde som du kan utöka. Om du behöver lÀgga till en anpassad formateringsfunktion, skulle du vanligtvis skriva din egen TypeScript-funktion som anvÀnder date-fns internt.
LÄt oss anvÀnda ett annat, mer passande exempel: Augmentering av en hypotetisk configuration-modul.
Anta att du har ett config-bibliotek som tillhandahÄller applikationsinstÀllningar.
1. Ursprungligt bibliotek (config.ts - konceptuellt):
// This is how the library might be structured internally
export interface AppConfig {
apiUrl: string;
timeout: number;
}
export const config: AppConfig = { ... };
Nu behöver din applikation lÀgga till en environment-egenskap till denna konfiguration, som Àr specifik för ditt projekt.
2. Modulaugmenteringsfil (t.ex. src/types/config.d.ts):
// src/types/config.d.ts
import 'config'; // This signals augmentation for the 'config' module.
declare module 'config' {
// We are augmenting the existing AppConfig interface from the 'config' module.
interface AppConfig {
// Add our new property.
environment: 'development' | 'staging' | 'production';
// Add another custom property.
featureFlags: Record;
}
}
3. Implementationsfil (t.ex. src/config.ts):
Denna fil tillhandahÄller den faktiska JavaScript-implementeringen för de utökade egenskaperna. Det Àr avgörande att denna fil finns och Àr en del av din projektkompilering.
// src/config.ts
// We need to import the original configuration to extend it.
// If 'config' exports `config: AppConfig` directly, we would import that.
// For this example, let's assume we are overriding or extending the exported object.
// IMPORTANT: This file needs to physically exist and be compiled.
// It's not just type declarations.
// Import the original configuration (this assumes 'config' exports something).
// For simplicity, let's assume we are re-exporting and adding properties.
// In a real scenario, you might import the original config object and mutate it,
// or provide a new object that conforms to the augmented type.
// Let's assume the original 'config' module exports an object that we can add to.
// This is often done by re-exporting and adding properties.
// This requires the original module to be structured in a way that allows extension.
// If the original module exports `export const config = { apiUrl: '...', timeout: 5000 };`,
// we can't directly add to it at runtime without modifying the original module or its import.
// A common pattern is to have an initialization function or a default export that is an object.
// Let's redefine the 'config' object in our project, ensuring it has the augmented types.
// This means our project's `config.ts` will provide the implementation.
import { AppConfig as OriginalAppConfig } from 'config';
// Define the extended configuration type, which now includes our augmentations.
// This type is derived from the augmented `AppConfig` declaration.
interface ExtendedAppConfig extends OriginalAppConfig {
environment: 'development' | 'staging' | 'production';
featureFlags: Record;
}
// Provide the actual implementation for the configuration.
// This object must conform to the `ExtendedAppConfig` type.
export const config: ExtendedAppConfig = {
apiUrl: 'https://api.example.com',
timeout: 10000,
environment: process.env.NODE_ENV as 'development' | 'staging' | 'production' || 'development',
featureFlags: {
newUserDashboard: true,
internationalPricing: false,
},
};
// Optionally, if the original library expected a default export and we want to maintain that:
// export default config;
// If the original library exported `config` directly, you might do:
// export * from 'config'; // Import original exports
// export const config = { ...originalConfig, environment: '...', featureFlags: {...} }; // Override or extend
// The key is that this `config.ts` file provides the runtime values for `environment` and `featureFlags`.
4. AnvÀndning i din applikation (src/main.ts):
// src/main.ts
import { config } from './config'; // Import from your extended config file
console.log(`API URL: ${config.apiUrl}`);
console.log(`Current Environment: ${config.environment}`);
console.log(`New User Dashboard Enabled: ${config.featureFlags.newUserDashboard}`);
if (config.environment === 'production') {
console.log('Running in production mode.');
}
I detta exempel förstÄr TypeScript nu att config-objektet (frÄn vÄr src/config.ts) har egenskaperna environment och featureFlags, tack vare modulaugmenteringen i src/types/config.d.ts. Körtidsbeteendet tillhandahÄlls av src/config.ts.
Exempel 2: Augmentering av ett förfrÄgningsobjekt i ett ramverk
Ramverk som Express.js har ofta förfrÄgningsobjekt med fördefinierade egenskaper. Du kanske vill lÀgga till anpassade egenskaper till förfrÄgningsobjektet, till exempel den autentiserade anvÀndarens detaljer, inom middleware.
1. Augmenteringsfil (t.ex. src/types/express.d.ts):
// src/types/express.d.ts
import 'express'; // Signal augmentation for the 'express' module
declare global {
// Augmenting the global Express namespace is also common for frameworks.
// Or, if you prefer module augmentation for express module itself:
// declare module 'express' {
// interface Request {
// user?: { id: string; username: string; roles: string[]; };
// }
// }
// Using global augmentation is often more straightforward for framework request/response objects.
namespace Express {
interface Request {
// Define the type for the custom user property.
user?: {
id: string;
username: string;
roles: string[];
// Add any other relevant user details.
};
}
}
}
2. Middleware-implementering (src/middleware/auth.ts):
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
// This middleware will attach user information to the request object.
export const authenticateUser = (req: Request, res: Response, next: NextFunction) => {
// In a real app, you'd fetch this from a token, database, etc.
// For demonstration, we'll hardcode it.
const isAuthenticated = true; // Simulate authentication
if (isAuthenticated) {
// TypeScript now knows req.user is available and has the correct type
req.user = {
id: 'user-123',
username: 'alice_wonder',
roles: ['admin', 'editor'],
};
console.log(`User authenticated: ${req.user.username}`);
} else {
console.log('Authentication failed.');
// Handle unauthenticated access (e.g., send 401)
return res.status(401).send('Unauthorized');
}
next(); // Pass control to the next middleware or route handler
};
3. AnvÀndning i din Express-app (src/app.ts):
// src/app.ts
import express, { Request, Response } from 'express';
import { authenticateUser } from './middleware/auth';
const app = express();
const port = 3000;
// Apply the authentication middleware to all routes or specific ones.
app.use(authenticateUser);
// A protected route that uses the augmented req.user property.
app.get('/profile', (req: Request, res: Response) => {
// TypeScript correctly infers req.user exists and has the expected properties.
if (req.user) {
res.send(`Welcome, ${req.user.username}! Your roles are: ${req.user.roles.join(', ')}.`);
} else {
// This case should theoretically not be reached if middleware works correctly,
// but it's good practice for exhaustive checks.
res.status(401).send('Not authenticated.');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
Detta visar hur modulaugmentering sömlöst kan integrera anpassad logik i ramverkstyper, vilket gör din kod mer lÀsbar, underhÄllbar och typsÀker för hela ditt utvecklingsteam.
Viktiga övervÀganden och bÀsta praxis
Ăven om modulaugmentering Ă€r ett kraftfullt verktyg Ă€r det viktigt att anvĂ€nda det med omdöme. HĂ€r Ă€r nĂ„gra bĂ€sta praxis att tĂ€nka pĂ„:
-
Föredra augmenteringar pÄ paketnivÄ: NÀr det Àr möjligt, sikta pÄ att augmentera moduler som explicit exporteras av tredjepartsbiblioteket (t.ex.
import 'library-name';). Detta Àr renare Àn att förlita sig pÄ global augmentering för bibliotek som inte Àr verkligt globala. -
AnvÀnd deklarationsfiler (.d.ts): Placera dina modulaugmenteringar i dedikerade
.d.ts-filer. Detta hÄller dina typaugmenteringar Ätskilda frÄn din körtidskod och organiserade. En vanlig konvention Àr att skapa en katalogsrc/types. - Var specifik: Augmentera endast det du verkligen behöver. Undvik att överutöka bibliotekstyper i onödan, eftersom detta kan leda till förvirring och göra din kod svÄrare att förstÄ för andra.
- TillhandahÄll körtidsimplementering: Kom ihÄg att modulaugmentering Àr en kompileringstidsfunktion. Du mÄste tillhandahÄlla körtidsimplementeringen för alla nya egenskaper eller metoder du lÀgger till. Denna implementering bör finnas i ditt projekts TypeScript- eller JavaScript-filer.
- Se upp för flera augmenteringar: Om flera delar av din kodbas eller olika bibliotek försöker augmentera samma modul pÄ motstridiga sÀtt kan det leda till ovÀntat beteende. Koordinera augmenteringar inom ditt team.
-
FörstÄ bibliotekets struktur: För att augmentera en modul effektivt mÄste du förstÄ hur biblioteket exporterar sina typer och vÀrden. Granska bibliotekets
index.d.ts-fil inode_modules/@types/library-nameför att identifiera de typer du behöver rikta in dig pÄ. -
ĂvervĂ€g nyckelordet
globalför ramverk: För att augmentera globala objekt som tillhandahÄlls av ramverk (som Expresss Request/Response), Àr det ofta mer lÀmpligt och renare att anvÀndadeclare globalÀn modulaugmentering. - Dokumentation Àr nyckeln: Om ditt projekt starkt förlitar sig pÄ modulaugmentering, dokumentera dessa augmenteringar tydligt. Förklara varför de Àr nödvÀndiga och var deras implementeringar kan hittas. Detta Àr sÀrskilt viktigt för att introducera nya utvecklare globalt.
NÀr ska modulaugmentering anvÀndas (och nÀr inte)
AnvÀnd nÀr:
- LÀgger till applikationsspecifika egenskaper: Som att lÀgga till anvÀndardata till ett förfrÄgningsobjekt eller anpassade fÀlt till konfigurationsobjekt.
- Integrerar med befintliga typer: Utökar grÀnssnitt eller typer för att överensstÀmma med din applikations mönster.
- FörbÀttrar utvecklarupplevelsen: TillhandahÄller bÀttre autokomplettering och typkontroll för tredjepartsbibliotek inom din specifika kontext.
- Arbetar med Àldre JavaScript: Augmenterar typer för Àldre bibliotek som kanske inte har omfattande TypeScript-definitioner.
Undvik nÀr:
- Drastiskt Àndrar kÀrnbibliotekets beteende: Om du finner dig sjÀlv behöva skriva om betydande delar av ett biblioteks funktionalitet, kan det vara ett tecken pÄ att biblioteket inte passar bra, eller att du bör övervÀga att forka eller bidra uppströms.
- Inför brytande Àndringar för konsumenter av det ursprungliga biblioteket: Om du augmenterar ett bibliotek pÄ ett sÀtt som skulle bryta kod som förvÀntar sig de ursprungliga, oförÀndrade typerna, var mycket försiktig. Detta Àr vanligtvis reserverat för interna projektaugmenteringar.
- NÀr en enkel omslagsfunktion rÀcker: Om du bara behöver lÀgga till nÄgra verktygsfunktioner som anvÀnder ett bibliotek, kan det vara enklare att skapa en fristÄende omslagsmodul Àn att försöka komplex modulaugmentering.
Modulaugmentering kontra andra metoder
Det Àr bra att jÀmföra modulaugmentering med andra vanliga mönster för att interagera med tredjepartskod:
- Omslagsfunktioner/klasser: Detta innebÀr att skapa dina egna funktioner eller klasser som internt anvÀnder tredjepartsbiblioteket. Detta Àr en bra metod för att kapsla in biblioteksanvÀndning och tillhandahÄlla ett enklare API, men det Àndrar inte direkt typerna av det ursprungliga biblioteket för konsumtion nÄgon annanstans.
- GrÀnssnittssammanslagning (inom dina egna typer): Om du har kontroll över alla inblandade typer kan du helt enkelt slÄ ihop grÀnssnitt inom din egen kodbas. Modulaugmentering riktar sig specifikt till *externa* modultyper.
- Bidra uppströms: Om du identifierar en saknad typ eller ett vanligt behov, Àr den bÀsta lÄngsiktiga lösningen ofta att bidra med Àndringar direkt till tredjepartsbiblioteket eller dess typdefinitioner (pÄ DefinitelyTyped). Modulaugmentering Àr en kraftfull lösning nÀr direkt bidrag inte Àr genomförbart eller omedelbart.
Globala övervÀganden för internationella team
NÀr man arbetar i en global teammiljö blir modulaugmentering Ànnu viktigare för att etablera konsekvens:
- Standardiserade metoder: Modulaugmentering gör det möjligt att genomdriva konsekventa sÀtt att hantera data (t.ex. datumformat, valutarepresentationer) över olika delar av din applikation och av olika utvecklare, oavsett deras lokala konventioner.
- Enhetlig utvecklarupplevelse: Genom att augmentera bibliotek för att passa ditt projekts standarder sÀkerstÀller du att alla utvecklare, frÄn Europa till Asien till Amerika, har tillgÄng till samma typinformation, vilket leder till fÀrre missförstÄnd och ett smidigare utvecklingsflöde.
-
Centraliserade typdefinitioner: Att placera augmenteringar i en delad
src/types-katalog gör dessa utökningar upptÀckbara och hanterbara för hela teamet. Detta fungerar som en central punkt för att förstÄ hur externa bibliotek anpassas. - Hantering av internationalisering (i18n) och lokalisering (l10n): Modulaugmentering kan vara avgörande för att skrÀddarsy bibliotek för att stödja i18n/l10n-krav. Till exempel, att augmentera ett UI-komponentbibliotek för att inkludera anpassade sprÄkstrÀngar eller datum/tidsformateringsadaptrar.
Slutsats
Modulaugmentering Àr en oumbÀrlig teknik i TypeScript-utvecklarens verktygslÄda. Den ger oss möjlighet att anpassa och utöka funktionaliteten hos tredjepartsbibliotek, vilket överbryggar klyftan mellan extern kod och vÄr applikations specifika behov. Genom att utnyttja deklarationssammanslagning kan vi förbÀttra typsÀkerheten, utvecklingsverktygen och upprÀtthÄlla en renare, mer konsekvent kodbas.
Oavsett om du integrerar ett nytt bibliotek, utökar ett befintligt ramverk eller sÀkerstÀller konsekvens över ett distribuerat globalt team, tillhandahÄller modulaugmentering en robust och flexibel lösning. Kom ihÄg att anvÀnda den eftertÀnksamt, tillhandahÄlla tydliga körtidsimplementeringar och dokumentera dina augmenteringar för att frÀmja en kollaborativ och produktiv utvecklingsmiljö.
Att behÀrska modulaugmentering kommer utan tvekan att höja din förmÄga att bygga komplexa, typsÀkra applikationer som effektivt utnyttjar det stora JavaScript-ekosystemet.